今天我們繼續分享tips 6~10
。
有時候,我們會寫一些輔助的function
來判斷傳入值是否符合某些條件,符合回傳True
,不符合回傳False
。
# 06a
的模式,利用if-else
來實作。# 06a
def get_bool(iterable):
if len(iterable) > 10:
return True
else:
return False
if __name__ == '__main__':
print(get_bool(range(10)), get_bool(range(11))) # False, True
else
的部份,其實可以省略,所以也可以寫成# 06b
的模式。# 06b
def get_bool(iterable):
if len(iterable) > 10:
return True
return False
if __name__ == '__main__':
print(get_bool(range(10)), get_bool(range(11))) # False, True
len(iterable) > 10
本身就會回傳True
或是False
,可以直接返回。所以就有了# 06c
的模式。我們覺得# 06c
這種模式俗又有力,推薦您使用。# 06c
def get_bool(iterable):
return len(iterable) > 10
if __name__ == '__main__':
print(get_bool(range(10)), get_bool(range(11))) # False, True
當我們有一個內含多個str
的list
要寫入檔案,且每個str
需要自己獨立一行。
# 07a
中的模式,手動於每次寫入時,加入\n
。# 07a
text = ['abcde', '12345']
with open('file1.txt', 'w') as f:
for s in text:
f.write(s+'\n')
print
接受一個file
參數,預設是sys.stdout
,我們可以偷龍轉鳳將open
之後的f
指定給file
,讓print
來幫我們自動加入\n
,如# 07b
所示。# 07b
text = ['abcde', '12345']
with open('file2.txt', 'w') as f:
for s in text:
print(s, file=f)
print
可以接受多個參數,所以我們可以進一步改寫# 07b
為# 07c
,直接傳入*text
,並指定sep='\n'
來節省一個迴圈。# 07c
的寫法善用built-in function
,且相當pythonic,推薦給您。# 07c
text = ['abcde', '12345']
with open('file3.txt', 'w') as f:
print(*text, sep='\n', file=f)
最後我們做個檢查。# 07d
中可以使用()將context manager分成多行的語法,為Python 3.10新添增的。一般而言,context manager
的命名會偏長,有了這個語法幫忙後,code
看起來會清楚不少。
# 07d
with (open('file1.txt') as f1,
open('file2.txt') as f2,
open('file3.txt') as f3):
print(f1.read() == f2.read() == f3.read()) # True
當有一個list
包含一個tuple
時,如[('d', 4)]
的情況,該如何取得'd'
及4
呢?
一般的解法是先利用[('d', 4)][0]
來拿到內層的('d', 4)
,然後使用d, four = ('d', 4)
的tuple unpacking
,如# 08a
。
# 08a
from collections import Counter
iterable = 'a'*1 + 'b'*2 + 'c'*3 + 'd'*4
cnter = Counter(iterable)
# cnter.most_common(1) # [('d', 4)]
if __name__ == '__main__':
# tuple unpacking
d, four = cnter.most_common(1)[0]
print(f'{d=}, {four=}') # d='d', four=4
針對這種情況,我們會推薦使用list unpacking
[(d, four)] = [('d', 4)]
,如# 08b
。沒錯,[]
也能放在等號的左側。於此處使用list unpacking
的好處是可以免去最外層需使用list
來indexing
,且擁有類似像Rust
的語法,可以驗證左右側整體型式是否相同(list
包含一個tuple
,且tuple
內元素數量要左右相同)。
# 08b
from collections import Counter
iterable = 'a'*1 + 'b'*2 + 'c'*3 + 'd'*4
cnter = Counter(iterable)
# cnter.most_common(1) # [('d', 4)]
if __name__ == '__main__':
# list unpacking
[(d, four)] = cnter.most_common(1)
print(f'{d=}, {four=}') # d='d', four=4
此外list unpacking
也支援像tuple unpacking
一樣的*
語法,如# 08c
,來收集剩餘的元素。
# 08c
from collections import Counter
iterable = 'a'*1 + 'b'*2 + 'c'*3 + 'd'*4
cnter = Counter(iterable)
# cnter.most_common(1) # [('d', 4)]
if __name__ == '__main__':
# `*` can be used in list unpacking as well
[(a, *_)] = [('a', 'b', 'c')]
print(f'{a=}, {_=}') # a='a', _=['b', 'c']
list unpacking
雖然少見,但在這種特別的情形,我們會推薦使用。畢竟因為比較少用的關係,當自己在讀code
看到時,很容易會注意到,反而會格外留心檢查右側的整體型別,或許這也是件好事吧XD
一般大家會使用zip
的情況,應該是需要對多個iterable
打迴圈的時候才會用到,但我們發現zip
搭配dict
也有許多妙用。
當需要一個dict
,其keys
為a~z
,而values
為1~26
,您會如何建立呢(註1
)?
一般來說,您應該會利用dict-comprehension
,如# 09a
。
# 09a
from string import ascii_lowercase
if __name__ == '__main__':
# [str, int]
d = {k: v
for k, v in zip(ascii_lowercase, range(1, 27))}
但我們發覺# 09b
這種寫法更加優雅。zip
幫我們將ascii_lowercase
與count(1)
縫在一起,然後交給dict
幫忙生成。短短一行,我們靈活地使用了zip
與itertools.count
。
zip
的強大,不只在於縫製iterable
,也可以體現於拆iterable
。當您有一個dict
,您會如何同時取得keys
及values
呢?
dict.keys()
與dict.values()
。keys, values = zip(*d.items())
是一種拆掉d
的方法。這個方法得到的keys
與values
是tuple
型態,所以可以使用[]
來取值。而dict.keys()
得到的dict_keys
型態與dict.values()
得到的dict_values
型態,皆無法使用[]
。# 09b
from itertools import count
from string import ascii_lowercase
if __name__ == '__main__':
# [str, int]
d = dict(zip(ascii_lowercase, count(1)))
keys, values = zip(*d.items())
continue
減少縮排continue是一個於迴圈中離開當下這個cycle
,進入下一個cycle
的語法。
假設我們想要寫一個my_filter
function
(註2
),其要求如下:
func
及iterable
。func
會針對iterable
中每個element
給出True
或False
my_filter
會以generator
型式返回func
判斷為True
的元素。一般來說,我們會寫出如# 10a
的模式。但此時我們由於for
與if
,程式的主邏輯yield item
得出現於第三層。如果外面層數過多,例如有使用context manager
、多個迴圈或多個if
,整體程式碼會很難閱讀。
# 10a
def func(x):
try:
return x > 10
except TypeError:
return False
def my_filter(func, iterable):
"""`yield item` locates at the second level of indentation"""
for item in iterable:
if func(item):
yield item
if __name__ == '__main__':
flt = my_filter(func, [2, 11, 'str', (), []])
print(list(flt)) # [11]
如果我們搭配not
與continue
,則會寫出# 10b
的模式。這麼一來我們就只縮排了for
這一層,程式的主邏輯yield item
可以出現於第二層。
# 10b
def func(x):
try:
return x > 10
except TypeError:
return False
def my_filter(func, iterable):
"""`yield item` locates at the first level of indentation
by using `continue` to save one level of indentation"""
for item in iterable:
if not func(item):
continue
yield item
if __name__ == '__main__':
flt = my_filter(func, [2, 11, 'str', (), []])
print(list(flt)) # [11]
# 10a
的方式較為直觀,但如若程式碼已縮排多層又很難重構的話,可以嘗試採用# 10b
的寫法。
註1:如果想要反過來,建立一個keys
為1~26
,values
為a-z
的dict
,可以這麼做:
# 09c
from string import ascii_lowercase
if __name__ == '__main__':
# [int, str]
d = dict(enumerate(ascii_lowercase, 1))
註2:Python的filter
實作大致如下,我們實作的是個閹割的版本。
# 10c
def my_filter(func, iterable):
"""Emulate built-in filter"""
if func is not None:
return (item for item in iterable if func(item))
return (item for item in iterable if item)